package roborally.model;

import be.kuleuven.cs.som.annotate.*;

/**
 * A class representing EnergyAmounts in RoboRally. An EnergyAmount is expressed in Ws.
 * @invar	An EnergyAmount is always positive.
 *			|isValidEnergyAmount(getAmount())
 * @Implements	Comparable<EnergyAmount>
 */
@Value
public class EnergyAmount implements Comparable<EnergyAmount> {
	/**
	 * Constuctor for an energy amount.
	 * @param	energyAmount
	 * 			A given energy level expressed in Ws.
	 * @pre		The given amount is a valid energy amount.
	 * 			|isValidEnergy(energyAmount, Double.MAX_VALUE)
	 */
	public EnergyAmount(double energyAmount)
	{
		this(energyAmount, EnergyUnit.WS);
	}

	/**
	 * Constuctor for an energy amount.
	 * @param	energyAmount
	 * 			A given energy level expressed in Ws.
	 * @pre		The given amount is a valid energy amount.
	 * 			|isValidEnergy(energyAmount, Double.MAX_VALUE)
	 * @pre		The given unit should be effective
	 * 			|unit != null
	 */
	public EnergyAmount(double energyAmount, EnergyUnit unit) throws IllegalArgumentException
	{
		assert isValidEnergyAmount(energyAmount);
		this.energyAmount = energyAmount;
		this.unit = unit;
	}
	
	/**
	 * A method that returns the energy content, as a double, of an EnergyAmount object
	 * @return energyAmount
	 */
	@Basic @Immutable
	public double getAmount(){ return this.energyAmount; }
	/**
	 * A variable indicating the energy content of an EnergyAmount object
	 */
	private final double energyAmount;
	@Basic @Immutable
	public EnergyUnit getUnit(){ return this.unit; }
	/**
	 * A variable representing the unit of this EnergyAmount object
	 */
	private final EnergyUnit unit;
	/**
	 * Returns an energyAmount equal to this energyAmount but expressed in the given unit.
	 * @param unit
	 * @return |result == new EnergyAmount(getAmount() * getUnit().toEnergyUnit(unit), unit)
	 */
	public EnergyAmount toUnit(EnergyUnit unit){
		return new EnergyAmount(getAmount() * getUnit().toEnergyUnit(unit), unit);
	}
	
	/**
	 * checks whether a given scalar is a valid as an energyAmount.
	 * @param energyAmount
	 * @return True is returned if the scalar is positive. Else false is returned.
	 */
	public static boolean isValidEnergyAmount(double energyAmount)
	{
		return energyAmount >= 0;
	}
	
	/**
	 * Checks whether or not the given EnergyAmounts are valid as energy-level and maximum energy-level for any Element that has these attributes.
	 * @param 	energy
	 * 			an energy-level
	 * @param 	maxEnergy
	 * 			the maximum energy-level
	 * @return	Returns true when the energy-level is smaller than or equal to the maximum energy-level. If not, false is returned. 
	 * 			|result == (energy.getAmount() <= maxEnergy.toUnit(energy.getUnit()).getAmount())
	 * @note	EnergyAmount has as invar that it is always greater or equal to 0, it is a precondition when instantiating EnergyAmount objects so we don not have to check that again.
	 */
	public static boolean isValidEnergy(EnergyAmount energy, EnergyAmount maxEnergy)
	{
		return (energy.getAmount() <= maxEnergy.toUnit(energy.getUnit()).getAmount());
	}
	/**
	 * @pre		...
	 * 			|other != null
	 * @pre		the energyAmount given as parameter is larger than zero.
	 * 			|energyAmount.getAmount() > 0
	 * @pre 	the energyAmount given as parameter should be larger or equal to this energyAmount.
	 * 			|energyAmount.getAmount() >= this.getAmount()
	 * @return
	 */
	public double getFraction(EnergyAmount energyAmount) throws IllegalArgumentException{
		assert energyAmount.getAmount() > 0;
		assert energyAmount.getAmount() >= this.getAmount();
		return getAmount()/energyAmount.toUnit(getUnit()).getAmount();
	}
	
	/**
	 * A method that, when invoked on an EnergyAmount object, returns a new EnergyAmount object with an energy content equal to the difference between getAmount() and other.getAmount() 
	 * @param other
	 * @pre		...
	 * 			|other != null
	 * @pre		...
	 * 			|this.getAmount() - other.toUnit(getUnit()).getAmount() >= 0
	 * @return
	 * 			|new EnergyAmount( getAmount() - other.toUnit(getUnit()).getAmount(), getUnit() )
	 */
	@Raw
	public EnergyAmount subtract(EnergyAmount other)
	{
		assert (this.getAmount() - other.toUnit(getUnit()).getAmount()) >= 0;
		return new EnergyAmount( getAmount() - other.toUnit(getUnit()).getAmount(), getUnit() );
	}
	/**
	 * A method that, when invoked on an EnergyAmount object, returns a new EnergyAmount object with an energy content equal to the sum off getAmount() and other.getAmount() 
	 * @param other
	 * @pre		...
	 * 			|other != null
	 * @pre		...
	 * 			|this.getAmount() <= Double.MAX_VALUE - other.toUnit(getUnit()).getAmount()
	 * @return	...
	 * 			|result == new EnergyAmount( this.getAmount() + other.toUnit(getUnit()).getAmount(), getUnit() )
	 */
	@Raw
	public EnergyAmount add(EnergyAmount other)
	{ 
		assert this.getAmount() <= (Double.MAX_VALUE - other.toUnit(getUnit()).getAmount());
		return new EnergyAmount( this.getAmount() + other.toUnit(getUnit()).getAmount(), getUnit() );
	}

	/**
	 * Compares this EnergyAmount with the given one as parameter.
	 * If this EnergyAmount is larger, 1 is returned.
	 * Else if this EnergyAmount is smaller, -1 is returned.
	 * In case this EnergyAmount and the other are equal, 0 is returned.
	 */
	@Override
	public int compareTo(EnergyAmount other){
		if(this.getAmount() < other.toUnit(getUnit()).getAmount()) return -1;
		if(this.getAmount() > other.toUnit(getUnit()).getAmount()) return 1;
		else return 0;
	}
	/**
	 * 
	 * @param 	other
	 * 			An energyamount
	 * @return	Returns true if the energyamounts are equal. If not, return false. If other is not an instance of an EnergyAmount false is returned
	 * 			result == this.compareTo((EnergyAmount)other) == 0 && other instanceof EnergyAmount
	 */
	@Override
	public boolean equals(Object other){
		if(!(other instanceof EnergyAmount)) return false;
		return this.compareTo((EnergyAmount)other) == 0;
	}
	/**
	 * Override hashcode so that EnergyAmounts with the energyamount have the same hashcode.
	 */
	@Override
	public int hashCode(){
		String amount = ""+getAmount();
		amount.replace('.', '0');
		return Integer.parseInt(amount);
	}
	/**
	 * @return A string representing the amount and the unit of an energyamount object
	 */
	@Override
	public String toString(){
		return ""+this.getAmount()+" "+this.getUnit();
	}
}
